package bman.game.engine;

import java.awt.Dimension;
import java.net.BindException;
import java.net.Socket;
import java.util.List;
import java.util.logging.Logger;

import bman.game.battletank.BattleTank;
import bman.tools.net.Radio;
import bman.tools.net.Receiver;
import static bman.game.engine.Entity.*;

/**
 * GameConsole serves as the main interface for a Game Client. The console provides a Game Client
 * with the following:
 * <p>
 * <ul>
 * <li>getGraphicsEngine() - Returns a JPanel where the game's graphics is rendered.</li>
 * <li>getController() - Pass all KeyEvents to this object to send commands to the GameConsole.</li>
 * <li>loadGame(Game) - Pass in a Game implementation. </li>
 * <li>connect() - Connect to another GameConsole</li>
 * </ul>
 * 
 * @author MrJacky
 *
 */

public class GameConsole implements Receiver
{
	Logger log = Logger.getLogger(this.getClass().getName());
	
	public static final String START = "START";
	
	Radio radio;
	GraphicsEngine ge;
	Game game;
	
	String ip = "127.0.0.1:888";
	int listenPort = 888;
	
	public GameConsole()
	{
		log.info("Starting up GameConsole....opening server port...");
		for(int i = 777;i<900;i++)
		{
			try
			{
				radio = new Radio(this,i);
				this.setPort(i);
				break;
			}
			catch(Exception e)
			{
				if(e instanceof BindException)
				{
					log.info("Port "+i+" is in use. Radio will look of other free port to bind...");
				}
				else
				{
					throw new RuntimeException("Unable to create Radio",e);
				}
			}
		}
		log.info("\n\n\nGameConsole created. Server port = "+listenPort+"\n\n\n");
	}
	
	public GameConsole(int recievePort) throws Exception
	{
		 radio = new Radio(this,recievePort);
	}
	
	public void loadGame(Game game)
	{
		this.game = game;
		GraphicsEngine g = getGraphicsEngine();
		g.setMap(game.getMap());
		g.setPreferredSize(game.getSize());
		log.info("game size: "+g.getSize());
		g.setTextFeed(game.getTextFeed());
		game.getTextFeed().addText("Radio is listening to port: "+this.listenPort);
	}
	
	public GraphicsEngine getGraphicsEngine()
	{
		if(ge == null)
		{
			ge = new GraphicsEngine();
		}
		return ge;
	}
	
	public Controller getController()
	{
		return new Controller(this);		
	}
	
	public void performAction(String action)
	{
		if(START.equals(action))
		{
			game.init();
			return;
		}
		
		List<String> response = game.process(action);
		log.info("Broadcasting "+response.size()+" changes");
		for (String resp : response)
		{
			radio.broadcast(resp.getBytes());	
		}
	}
	
	/**
	 * Sets the port were this GameConsole will listen for
	 * incoming connections.
	 */
	public void setPort(int port)
	{
		this.listenPort = port;
	}
	
	/**
	 * Connects to another GameConsole. When a GameConsole connects the first thing it does
	 * is to send the "INIT" command (to the GameConsole it is connecting to). 
	 * <p>
	 * The GameMap will be re-initialized (the old map will be discarded).
	 * <p>
	 * This method will also initialize the GameConsole's Radio receiver
	 * which means the GameConsole will not listen for any incoming connections.
	 * 
	 * @param address - A host or IP address. The port may be included, for example: 127.0.0.1:777
	 * @return true if connection is successful, false if not.
	 */
	public boolean connect(String address)
	{
		try
		{
			radio.send(address, ("INIT REPLY_PORT:"+this.listenPort).getBytes());
			game.reset();
			return true;
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return false;
		}
	}
	
	/**
	 * Messages received by the console will be forwarded Game.processRemote()
	 * method.
	 * 
	 * The exceptions are:
	 * <ol>
	 * <li>If the message contains INIT, GameConsole will broadcast the address 
	 *     of the one requesting for initialization.
	 * </li>
	 * <li>If the message starts with ADD, GameConsole will send it a WELCOM message
	 * </li>
	 * </ol>
	 */
	public void receive(Socket sourceSocket, byte[] b)
	{
		String s = new String(b);
		
		log.fine("Command recieved: "+s);
		
		if(s.startsWith("INIT"))
		{
			String replyPort = getField(s,"REPLY_PORT");
			String address = sourceSocket.getInetAddress().getHostAddress();
			radio.broadcast(("ADD:"+address+":"+replyPort).getBytes());
		}

		if(s.startsWith("ADD"))
		{
			String address = getField(s,"ADD");
			log.info("Sending Welcom message to new client: "+address);
			radio.send(address,"CONSOLE MESSAGE: WELCOME".getBytes());
			return;
		}

		List<String> response = game.processRemote(s);
		
		for (String resp : response)
		{
			radio.reply(sourceSocket,resp.getBytes());
		}
	}
	
	/**
	 * Format of source is "ID:ABC,X:123,Y:456,Z:7,etc,etc"
	 * <p>
	 * getField("ID") will return ABC <br>
	 * getField("X") will return 123 <br>
	 * getField("Y") will return 456 <br>
	 * getField("Z") will return 7 <br>
	 * etc, etc
	 * Returns null is a non-existent field is asked.
	 *
	 */
	public static String getField(String source,String fieldName)
	{
		if (source.indexOf(fieldName) == -1)
		{
			return null;
		}
		
		int start =source.indexOf(fieldName+":") +fieldName.length()+1;
		int end =source.indexOf(',',start);
		
		if (end == -1)
		{
			end =source.length();
		}
		
		return source.substring(start,end);
	}
}

